/*
 * Copyright © 2015 Cask Data, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package co.cask.cdap.data2.dataset2.lib.partitioned;

import co.cask.cdap.api.common.Bytes;
import co.cask.cdap.api.dataset.lib.Partitioning.FieldType;

/**
 * Common utility methods when dealing wit partitioning field types.
 */
public class FieldTypes {

  /**
   * Parse a string into a value of this field type. For example, {@link FieldType#INT} delegates this
   * to {@link Integer#parseInt}.
   * @param str the string to parse
   */
  public static <T extends Comparable> T parse(String str, FieldType type) {
    Comparable value;
    switch(type) {
      case STRING:
        value = str;
        break;
      case LONG:
        value = Long.parseLong(str);
        break;
      case INT:
        value = Integer.parseInt(str);
        break;
      default:
        throw new IllegalArgumentException("Unhandled field type: " + type.name());
    }
    @SuppressWarnings("unchecked")
    T t = (T) value;
    return t;
  }

  /**
   * Convert a value of this field type to a byte array.
   * @param value the value to convert
   * @param <T> the Java type represented by this field type
   */
  public static <T extends Comparable> byte[] toBytes(T value, FieldType type) {
    switch(type) {
      case STRING:
        if (value instanceof String) {
          return Bytes.toBytes((String) value);
        }
        break;
      case LONG:
        if (value instanceof Long) {
          // trick to preserve the ordering of the generated byte array
          return Bytes.toBytes(((Long) value) ^ Long.MIN_VALUE);
        }
        break;
      case INT:
        if (value instanceof Integer) {
          // trick to preserve the ordering of the generated byte array
          return Bytes.toBytes(((Integer) value) ^ Integer.MIN_VALUE);
        }
        break;
      default:
        throw new IllegalArgumentException("Unhandled field type: " + type.name());
    }
    throw new IllegalArgumentException(String.format(
      "Incompatible value %s of type %s for field type %s.", value, value.getClass(), type.name()));
  }

  /**
   * @return the number of bytes that need to be read to deserialize a value of this field type
   *   at the given position in the byte array. For integer and long values, this is constant,
   *   but for strings, this is variable depending on the length of the string.
   */
  public static int determineLengthInBytes(byte[] bytes, int offset, FieldType type) {
    switch(type) {
      case STRING:
        for (int i = offset; i < bytes.length; i++) {
          if (bytes[i] == 0) {
            return i - offset;
          }
        }
        return bytes.length - offset;
      case LONG:
        return Bytes.SIZEOF_LONG;
      case INT:
        return Bytes.SIZEOF_INT;
      default:
        throw new IllegalArgumentException("Unhandled field type: " + type.name());
    }
  }

  /**
   * Deserialize a value of this field type, starting at given offset in the byte array, consuming
   * the given number of bytes.
   */
  public static <T extends Comparable> T fromBytes(byte[] bytes, int offset, int length, FieldType type) {
    Comparable value;
    switch(type) {
      case STRING:
        value = Bytes.toString(bytes, offset, length);
        break;
      case LONG:
        value = Bytes.toLong(bytes, offset, length) ^ Long.MIN_VALUE;
        break;
      case INT:
        value = Bytes.toInt(bytes, offset, length) ^ Integer.MIN_VALUE;
        break;
      default:
        throw new IllegalArgumentException("Unhandled field type: " + type.name());
    }
    @SuppressWarnings("unchecked")
    T t = (T) value;
    return t;
  }

  /**
   * Validate that a given value has the type that is required by this field type.
   */
  public static boolean validateType(Comparable value, FieldType type) {
    switch(type) {
      case STRING:
        return value instanceof String;
      case LONG:
        return value instanceof Long;
      case INT:
        return value instanceof Integer;
      default:
        throw new IllegalArgumentException("Unhandled field type: " + type.name());
    }
  }

  /**
   * @return the type name used by the Hive DDL to represent this field type
   */
  public static String toHiveType(FieldType type) {
    switch(type) {
      case STRING:
        return "STRING";
      case LONG:
        return "BIGINT";
      case INT:
        return "INT";
      default:
        throw new IllegalArgumentException("Unhandled field type: " + type.name());
    }
  }

}
